package stub

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"mariadb-operator/pkg/api/backup"
	"mariadb-operator/pkg/api/cluster"
	"mariadb-operator/pkg/api/restore"
	"mariadb-operator/pkg/apis/finupgroup/v1alpha1"
	"mariadb-operator/pkg/constants"

	appv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/operator-framework/operator-sdk/pkg/k8sclient"
	"github.com/operator-framework/operator-sdk/pkg/sdk"
	"gopkg.in/robfig/cron.v2"

	"github.com/sirupsen/logrus"
)

func NewHandler() sdk.Handler {
	return &Handler{}
}

type Handler struct {
	// Fill me
}

var (
	crontab = cron.New()
)

func init() {
	crontab.Start()
	// notifyCronBackupTask()
}

// Handle mariadb-operator logic handle
func (h *Handler) Handle(ctx context.Context, event sdk.Event) error {
	switch o := event.Object.(type) {
	case *appv1.StatefulSet:
		if event.Deleted {
			return nil
		}
		cluster := &v1alpha1.MariaDBCluster{
			TypeMeta:   metav1.TypeMeta{APIVersion: "finupgroup.com/v1alpha1", Kind: "MariaDBCluster"},
			ObjectMeta: metav1.ObjectMeta{Name: o.Name, Namespace: o.Namespace},
		}
		err := sdk.Get(cluster)
		if err != nil {
			logrus.Errorf("update mariadb cluster status err : %v", err)
		} else {
			if equeue(cluster.Status.StatefulSetStatus, o.Status) {
				return nil
			}
			cluster.Status = v1alpha1.MariaDBClusterStatus{StatefulSetStatus: o.Status}
			err := sdk.Update(cluster)
			if err != nil {
				return fmt.Errorf("Failed to update maridb cluster %v  in namespace %v : %v", o.Name, o.Namespace, err)
			}
			logrus.Infof("update mariadb clsuter %v in namespace %v success.", o.Name, o.Namespace)
		}
	case *v1alpha1.MariaDBCluster:
		if event.Deleted {
			err := cluster.Delete(o)
			if err != nil {
				return fmt.Errorf("Failed to delete mariadb cluster %v in namespace %v : %v", o.Name, o.Namespace, err)
			}
			logrus.Infof("delete mariadb clsuter %v in namespace %v success.", o.Name, o.Namespace)
		} else {
			if cluster.Exist(o) {
				if !cluster.NeedUpdate(o) {
					return nil
				}
				err := cluster.Update(o)
				if err != nil {
					return fmt.Errorf("Failed to update maridb cluster %v  in namespace %v : %v", o.Name, o.Namespace, err)
				}
				logrus.Infof("update mariadb clsuter %v in namespace %v success.", o.Name, o.Namespace)
			} else {
				err := cluster.Create(o)
				if err != nil {
					return fmt.Errorf("Failed to create maridb cluster %v  in namespace %v : %v", o.Name, o.Namespace, err)
				}
				logrus.Infof("create mariadb clsuter %v in namespace %v success.", o.Name, o.Namespace)
			}
		}

	case *corev1.ConfigMap:
		clusterName := o.Annotations[constants.ReferenceMariaDBClusterKey]
		restartPolicy := o.Annotations[constants.RestartPolicyKey]
		ss, err := k8sclient.GetKubeClient().AppsV1().StatefulSets(o.Namespace).Get(clusterName, metav1.GetOptions{})
		if err != nil {
			if errors.IsNotFound(err) { //说明该configmap是第一次创建，对应的statefulset还没创建成功，所以这里不需要重启mariadb集群，否则肯定是更新了configmap，所以需要重启
				return nil
			}
			return fmt.Errorf("when restart mariadb cluster %v  in namespace %v , failed to get mariadb cluster : %v", o.Name, o.Namespace, err)
		}

		if clusterName != "" {
			if err := cluster.Restart(clusterName, o.Namespace, restartPolicy, ss); err != nil {
				return fmt.Errorf("Failed to restart mariadb cluster : %v", err)
			}
		}

	case *v1alpha1.MariaDBBackup:
		if event.Deleted {
			logrus.Infof("delete backup %v in namespace %v success.", o.Name, o.Namespace)
		} else {
			if o.Annotations != nil && o.Annotations["produced"] == "true" {
				return nil
			}
			srt, err := k8sclient.GetKubeClient().CoreV1().Secrets(o.Namespace).Get(constants.DefaultClusterSecretName, metav1.GetOptions{})
			if err != nil {
				return fmt.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.Cluster, err)
			}
			cmd := ""
			user := string(srt.Data[constants.SecretUserKey])
			pwd := string(srt.Data[constants.SecretPasswordKey])
			databases := strings.Join(strings.Split(o.Spec.Executor.Databases, ","), " ")
			if databases == constants.BackupAllDatabases {
				cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v -A | gzip > %v", o.Spec.Cluster, o.Spec.Cluster, user, pwd, constants.BackupFileName)
			} else {
				cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v --databases %v | gzip > %v", o.Spec.Cluster, o.Spec.Cluster, user, pwd, databases, constants.BackupFileName)
			}

			o.Annotations = map[string]string{"produced": "true"}
			sdk.Update(o)

			task := &v1alpha1.BackupTask{BackupName: o.Name, BackupNamespace: o.Namespace, ClusterName: o.Spec.Cluster, Command: cmd, Type: "backup"}
			if err := backup.ProduceTask(task); err != nil {
				return fmt.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.Cluster, err)
			}
			logrus.Infof("backup mariadb cluster %v data success", o.Spec.Cluster)
		}

	case *v1alpha1.MariaDBCronBackup:
		enntryID, _ := strconv.Atoi(o.Annotations["entryID"])
		if event.Deleted {
			crontab.Remove((cron.EntryID)(enntryID))
			logrus.Infof("delete cronbackup s %v in namespace %v success.", o.Name, o.Namespace)
		} else {
			if o.Annotations != nil && o.Annotations["entryID"] != "" {
				logrus.Infof("annotations %#v ", o.Annotations)
				if o.Annotations["scheduler"] != o.Spec.Schedule { //need to update cron job
					enntryID, _ := strconv.Atoi(o.Annotations["entryID"])
					crontab.Remove((cron.EntryID)(enntryID))
					id, err := crontab.AddFunc(o.Spec.Schedule, func() {
						srt, err := k8sclient.GetKubeClient().CoreV1().Secrets(o.Namespace).Get(constants.DefaultClusterSecretName, metav1.GetOptions{})
						if err != nil {
							logrus.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.BackupTemplate.Cluster, err)
							return
						}
						cmd := ""
						user := string(srt.Data[constants.SecretUserKey])
						pwd := string(srt.Data[constants.SecretPasswordKey])
						databases := strings.Join(strings.Split(o.Spec.BackupTemplate.Executor.Databases, ","), " ")
						if databases == constants.BackupAllDatabases {
							cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v -A | gzip > %v", o.Spec.BackupTemplate.Cluster, o.Spec.BackupTemplate.Cluster, user, pwd, constants.BackupFileName)
						} else {
							cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v --databases %v | gzip > %v", o.Spec.BackupTemplate.Cluster, o.Spec.BackupTemplate.Cluster, user, pwd, databases, constants.BackupFileName)
						}
						task := &v1alpha1.BackupTask{BackupName: o.Name, BackupNamespace: o.Namespace, ClusterName: o.Spec.BackupTemplate.Cluster, Command: cmd, Type: "crobBackup"}
						if err := backup.ProduceTask(task); err != nil {
							logrus.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.BackupTemplate.Cluster, err)
						}
						logrus.Infof("create cronbackup task %v in namespace %v success.", o.Name, o.Namespace)
					})
					if err != nil {
						return fmt.Errorf("Failed to update cronbackup %v in namespace %v. : %v", o.Name, o.Namespace, err)
					}
					o.Annotations = map[string]string{"entryID": fmt.Sprintf("%v", (int)(id)), "scheduler": o.Spec.Schedule}
					sdk.Update(o)
					logrus.Infof("update cronbackup %v in namespace %v success.", o.Name, o.Namespace)
				}
				if !crontab.Exist((cron.EntryID)(enntryID)) {
					id, err := crontab.AddFunc(o.Spec.Schedule, func() {
						srt, err := k8sclient.GetKubeClient().CoreV1().Secrets(o.Namespace).Get(constants.DefaultClusterSecretName, metav1.GetOptions{})
						if err != nil {
							logrus.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.BackupTemplate.Cluster, err)
							return
						}
						cmd := ""
						user := string(srt.Data[constants.SecretUserKey])
						pwd := string(srt.Data[constants.SecretPasswordKey])
						databases := strings.Join(strings.Split(o.Spec.BackupTemplate.Executor.Databases, ","), " ")
						if databases == constants.BackupAllDatabases {
							cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v -A | gzip > %v", o.Spec.BackupTemplate.Cluster, o.Spec.BackupTemplate.Cluster, user, pwd, constants.BackupFileName)
						} else {
							cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v --databases %v | gzip > %v", o.Spec.BackupTemplate.Cluster, o.Spec.BackupTemplate.Cluster, user, pwd, databases, constants.BackupFileName)
						}
						task := &v1alpha1.BackupTask{BackupName: o.Name, BackupNamespace: o.Namespace, ClusterName: o.Spec.BackupTemplate.Cluster, Command: cmd, Type: "crobBackup"}
						if err := backup.ProduceTask(task); err != nil {
							logrus.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.BackupTemplate.Cluster, err)
						}
						logrus.Infof("create cronbackup task %v in namespace %v success.", o.Name, o.Namespace)
					})
					if err != nil {
						return fmt.Errorf("Failed to update cronbackup %v in namespace %v. : %v", o.Name, o.Namespace, err)
					}
					o.Annotations = map[string]string{"entryID": fmt.Sprintf("%v", (int)(id)), "scheduler": o.Spec.Schedule}
					sdk.Update(o)
					logrus.Infof("add cronbackup %v in namespace %v success.", o.Name, o.Namespace)
				}
			} else {
				logrus.Info("crontab.AddFunc")
				id, err := crontab.AddFunc(o.Spec.Schedule, func() {
					logrus.Infof("生成任务方法执行   %v", o.Spec.Schedule)
					srt, err := k8sclient.GetKubeClient().CoreV1().Secrets(o.Namespace).Get(constants.DefaultClusterSecretName, metav1.GetOptions{})
					if err != nil {
						logrus.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.BackupTemplate.Cluster, err)
						return
					}
					cmd := ""
					user := string(srt.Data[constants.SecretUserKey])
					pwd := string(srt.Data[constants.SecretPasswordKey])
					databases := strings.Join(strings.Split(o.Spec.BackupTemplate.Executor.Databases, ","), " ")
					if databases == constants.BackupAllDatabases {
						cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v -A | gzip > %v", o.Spec.BackupTemplate.Cluster, o.Spec.BackupTemplate.Cluster, user, pwd, constants.BackupFileName)
					} else {
						cmd = fmt.Sprintf("/usr/bin/mysqldump -h %v-0.%v -u%v -p%v --databases %v | gzip > %v", o.Spec.BackupTemplate.Cluster, o.Spec.BackupTemplate.Cluster, user, pwd, databases, constants.BackupFileName)
					}
					task := &v1alpha1.BackupTask{BackupName: o.Name, BackupNamespace: o.Namespace, ClusterName: o.Spec.BackupTemplate.Cluster, Command: cmd, Type: "crobBackup"}
					if err := backup.ProduceTask(task); err != nil {
						logrus.Errorf("backup mariadb cluster %v data failed: %v", o.Spec.BackupTemplate.Cluster, err)
					}
					logrus.Infof("create cronbackup task %v in namespace %v success.", o.Name, o.Namespace)
				})
				if err != nil {
					return fmt.Errorf("Failed to create cronbackup %v in namespace %v. : %v", o.Name, o.Namespace, err)
				}
				logrus.Infof("crontab.AddFunc  id = %v", (int)(id))
				o.Annotations = map[string]string{"entryID": fmt.Sprintf("%v", (int)(id)), "scheduler": o.Spec.Schedule}
				if err := sdk.Update(o); err != nil {
					return fmt.Errorf("Failed to update cronbackup %v in namespace %v. : %v", o.Name, o.Namespace, err)
				}
				logrus.Infof("create cronbackup  %v in namespace %v success.", o.Name, o.Namespace)
			}
		}
	case *v1alpha1.Restore:
		if event.Deleted {
			logrus.Infof("delete restore %v in namespace %v success.", o.Name, o.Namespace)
		}
		if o.Annotations != nil && o.Annotations["produced"] == "true" {
			return nil
		}
		srt, err := k8sclient.GetKubeClient().CoreV1().Secrets(o.Namespace).Get(constants.DefaultClusterSecretName, metav1.GetOptions{})
		if err != nil {
			return fmt.Errorf("restore mariadb cluster %v data failed: %v", o.Spec.ClusterReference, err)
		}
		user := string(srt.Data[constants.SecretUserKey])
		pwd := string(srt.Data[constants.SecretPasswordKey])
		dataFromFile := o.Spec.DataFrom[strings.LastIndex(o.Spec.DataFrom, "/")+1:]
		cmd := fmt.Sprintf("wget %v;\n gunzip < %v | mysql -h %v-0.%v -u%v -p%v", o.Spec.DataFrom, dataFromFile, o.Spec.ClusterReference, o.Spec.ClusterReference, user, pwd)
		task := &v1alpha1.RestoreTask{
			RestoreName:      o.Name,
			RestoreNamespace: o.Namespace,
			ClusterName:      o.Spec.ClusterReference,
			Command:          cmd,
			FileName:         dataFromFile,
		}
		if err := restore.ProduceTask(task); err != nil {
			return fmt.Errorf("produce restore %v's task  failed: %v", o.Name, err)
		}
		o.Annotations = map[string]string{"produced": "true"}
		sdk.Update(o)
		logrus.Info("produce restore task success")
	}
	return nil
}

func equeue(s1, s2 appv1.StatefulSetStatus) bool {
	if s1.CurrentReplicas == s2.CurrentReplicas &&
		// *(s1.CollisionCount) == *(s2.CollisionCount) &&
		s1.CurrentRevision == s2.CurrentRevision &&
		s1.ObservedGeneration == s2.ObservedGeneration &&
		s1.ReadyReplicas == s2.ReadyReplicas &&
		s1.Replicas == s2.Replicas &&
		s1.UpdateRevision == s2.UpdateRevision {
		return true
	}
	return false
}
